﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.Kinect.Nui;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MocKinect.Proxies
{
    public class ImageStreamProxy : BaseProxy
    {
        #region Mock members
        private bool _mockStreamIsOpened = false;
        private bool _realStreamIsOpened = false;
        public ImageStreamProxy(Runtime mocKinectRuntime, ImageStreamType streamType)
            : base(mocKinectRuntime)
        {
            StreamType = streamType;
        }

        private void SetImageStreamSizeFromResolution(ImageResolution resolution)
        {
            switch (resolution)
            {
                case ImageResolution.Invalid:
                    break;
                case ImageResolution.Resolution1280x1024:
                    Width = 1280;
                    Height = 1024;
                    break;
                case ImageResolution.Resolution320x240:
                    Width = 320;
                    Height = 240;
                    break;
                case ImageResolution.Resolution640x480:
                    Width = 640;
                    Height = 480;
                    break;
                case ImageResolution.Resolution80x60:
                    Width = 80;
                    Height = 60;
                    break;
                default:
                    break;
            }
        }

        #endregion

        #region Real Kinect members
        private ImageStream _realImageStream = null;
        internal ImageStream RealImageStream
        {
            get
            {
                if (mocKinectRuntime.RealKinectRuntime != null && _realImageStream == null)
                    switch (StreamType)
                    {
                        case ImageStreamType.Depth:
                            _realImageStream = mocKinectRuntime.RealKinectRuntime.DepthStream;
                            break;
                        case ImageStreamType.Video:
                            _realImageStream = mocKinectRuntime.RealKinectRuntime.VideoStream;
                            break;
                        case ImageStreamType.Invalid:
                        default:
                            break;
                    }
                return _realImageStream;
            }
        }

        public void OpenRealImageStreamIfNeeded()
        {
            if (!_mockStreamIsOpened) return;
            if (_realStreamIsOpened) return;
            if (mocKinectRuntime.RealKinectRuntime != null)
            {
                _realStreamIsOpened = true;
                switch (StreamType)
                {
                    case ImageStreamType.Depth:
                        if (mocKinectRuntime.HasDepthInvokations())
                            mocKinectRuntime.RealKinectRuntime.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(mocKinectRuntime.kinectRuntime_DepthFrameReady);
                        break;
                    case ImageStreamType.Video:
                        if (mocKinectRuntime.HasVideoInvokations())
                            mocKinectRuntime.RealKinectRuntime.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>(mocKinectRuntime.kinectRuntime_VideoFrameReady);
                        break;
                }
                if (RealImageStream != null)
                    RealImageStream.Open(StreamType, PoolSize, Resolution, ImageType);
            }
        }

        #endregion

        #region Kinect members
        public ImageStreamType StreamType { get; private set; }
        public ImageType ImageType { get; private set; }
        public int PoolSize { get; private set; }

        private int _width = 0;
        public int Width
        {
            get
            {
                return _width;

            }
            private set
            {
                _width = value;
            }
        }
        private int _height = 0;
        public int Height
        {
            get
            {
                return _height;
            }
            private set
            {
                _height = value;
            }
        }

        public ImageResolution Resolution { get; private set; }

        public void Open(ImageStreamType streamType, int poolSize, ImageResolution resolution, ImageType imageType)
        {
            StreamType = streamType;
            Resolution = resolution;
            PoolSize = poolSize;
            ImageType = imageType;
            SetImageStreamSizeFromResolution(resolution);
            _mockStreamIsOpened = true;
            // if recording or just proxy mode
            if (mocKinectRuntime.MockMode == MocKinectMode.Record
                || mocKinectRuntime.MockMode == MocKinectMode.Reality)
            {
                OpenRealImageStreamIfNeeded();
            }
        }

        public ImageFrame GetNextFrame(int MillisecondsWait)
        {
            ImageFrame frame = null;
            if (mocKinectRuntime.MockMode == MocKinectMode.Replay)
                return ReadNextFrame(MillisecondsWait);
            else
            {
                if (RealImageStream != null)
                {
                    frame = RealImageStream.GetNextFrame(MillisecondsWait);
                    if (mocKinectRuntime.MockMode == MocKinectMode.Record)
                        Record(frame);
                    return frame;
                }
            }
            return null;
        }

        #endregion

        #region Record/Replay members
        private SerialisableImageFrame[] serializedImagesBuffer = new SerialisableImageFrame[15];
        public int serializableImagesBufferIndex = 0;
        public int serializedImagesBufferIndex = 0;
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        private FileStream fileStream;
        private SerialisableImageFrame currentFrame = null;
        private long firstFrameRecordedTimestamp = 0;
        private DateTime firstFrameRealTimestamp;

        private string RecordFilename { get; set; }
        private string RecordFolder { get; set; }


        internal void FlushAndClose()
        {
            if (fileStream != null)
            {
                if (mocKinectRuntime.MockMode == MocKinectMode.Record)
                {
                    FlushRecordBuffer();
                }
                fileStream.Flush();
                fileStream.Close();
                fileStream = null;
                currentFrame = null;
                serializableImagesBufferIndex = 0;
            }
        }


        #endregion

        #region Record



        internal void InitPlaybackFolder(string recordFolder)
        {
            RecordFolder = recordFolder;
            RecordFilename = Path.Combine(RecordFolder, StreamType.ToString());

            if (mocKinectRuntime.MockMode == MocKinectMode.Replay || mocKinectRuntime.MockMode == MocKinectMode.Record)
            {
                fileStream = new FileStream(RecordFilename,
                    mocKinectRuntime.MockMode == MocKinectMode.Replay ? FileMode.Open : FileMode.Create);
                serializableImagesBufferIndex = 0;
            }
        }

        private void FlushRecordBuffer()
        {
            lock (serializedImagesBuffer)
            {
                serializedImagesBuffer.OrderBy(i => i.FrameNumber);
                for (int serializedImagesBufferIndex = 0; serializedImagesBufferIndex < serializedImagesBuffer.Length; serializedImagesBufferIndex++)
                {
                    if (serializedImagesBuffer[serializedImagesBufferIndex] != null)
                    {
                        serializedImagesBuffer[serializedImagesBufferIndex].Serialize(binaryFormatter, fileStream, RecordFilename);
                        //serialisableImageFrame.SerializeImage(RecordFilename);
                    }
                }
            }
        }

        public void Record(ImageFrame currentFrame)
        {
            lock (serializedImagesBuffer)
            {
                if (serializedImagesBuffer[serializableImagesBufferIndex] == null)
                    serializedImagesBuffer[serializableImagesBufferIndex] = new SerialisableImageFrame();
                serializedImagesBuffer[serializableImagesBufferIndex].SetFromImageFrame(currentFrame);
                //serializedImagesBuffer[serializedImagesBufferIndex].Serialize(binaryFormatter, fileStream, RecordFilename);
                serializableImagesBufferIndex++;
                if (serializableImagesBufferIndex == serializedImagesBuffer.Length / 2
                    || serializableImagesBufferIndex == serializedImagesBuffer.Length
                    )
                {
                    new Thread(new ThreadStart(FlushRecordBuffer)).Start();
                    if (serializableImagesBufferIndex == serializedImagesBuffer.Length)
                        serializableImagesBufferIndex = 0;
                }
            }
        }

        #endregion

        #region Replay

        private bool DeserializeFrame(int MillisecondsWait)
        {
            DateTime now = DateTime.Now;
            if (currentFrame == null)
            {
                lock (serializedImagesBuffer)
                {
                    if (fileStream.Position == fileStream.Length)
                    {
                        // rewind
                        fileStream.Seek(0, SeekOrigin.Begin);
                    }

                    if (serializableImagesBufferIndex == 0)
                    {
                        firstFrameRecordedTimestamp = 0;
                        for (serializedImagesBufferIndex = 0; serializedImagesBufferIndex < serializedImagesBuffer.Length && fileStream.Position < fileStream.Length; serializedImagesBufferIndex++)
                        {
                            serializedImagesBuffer[serializedImagesBufferIndex] = (SerialisableImageFrame)binaryFormatter.Deserialize(fileStream);
                            //serializedImagesBuffer = (SerialisableImageFrame[])binaryFormatter.Deserialize(fileStream);
                        }
                    }
                    currentFrame = serializedImagesBuffer[serializableImagesBufferIndex];
                }
            }
            //currentFrame = serializedImagesBuffer[serializedImagesBufferIndex];
            if (firstFrameRecordedTimestamp == 0)
            {
                firstFrameRecordedTimestamp = currentFrame.Timestamp;
                firstFrameRealTimestamp = now;
            }
            if (currentFrame.Timestamp - firstFrameRecordedTimestamp > (now - firstFrameRealTimestamp).TotalMilliseconds)
            {
                //Thread.Sleep(MillisecondsWait);
                //return false;
            }
            serializableImagesBufferIndex++;
            if (serializableImagesBufferIndex == serializedImagesBuffer.Length)
                serializableImagesBufferIndex = 0;
            return true;
        }

        internal ImageFrame ReadNextFrame(int MillisecondsWait)
        {
            ImageFrame imageFrame = null;
            if (DeserializeFrame(MillisecondsWait))
            {
                imageFrame = (ImageFrame)currentFrame;
                currentFrame = null;
            }
            return imageFrame;
        }

        #endregion

        #region Event based Replay
        private SynchronizationContext context;
        private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        public void Play()
        {
            List<object> frames = new List<object>();
            context = SynchronizationContext.Current;
            StreamReader reader = new StreamReader(RecordFilename);

            CancellationToken token = cancellationTokenSource.Token;

            //Task.Factory.StartNew(() =>
            //{
            //    foreach (ReplaySkeletonFrame frame in frames)
            //    {
            //        Thread.Sleep(TimeSpan.FromMilliseconds(frame.TimeStamp));

            //        if (token.IsCancellationRequested)
            //            return;

            //        ReplaySkeletonFrame closure = frame;
            //        context.Send(state =>
            //        {
            //            if (SkeletonFrameReady != null)
            //                SkeletonFrameReady(this, new ReplaySkeletonFrameReadyEventArgs { SkeletonFrame = closure });
            //        }, null);
            //    }
            //}, token);
        }
        #endregion
    }
}
